#!/bin/bash
set -o errexit
set -o pipefail

VERSION=0.1.2

smartos_docker_conf_file=/opt/tools/etc/smartos-docker.conf
# minimal-64-lts@20.4.0
temp_image_uuid=800db35c-5408-11eb-9792-872f658e7911
internal_namespace=itime

support_cmds="run|pull|ps|logs|images|start|stop|restart|rm|rmi|port|version|exec|help"


function usage_logs(){
    echo "  $(basename $0) logs [-f] <container>"
    echo "    show docker container logs"
    echo "    args:"
    echo "      <container> docker container name or uuid"
    echo "    options:"
    echo "      -f trace logs output"
}

function usage_pull(){
    echo "  $(basename $0) pull [-q] <docker_image>"
    echo "    pull docker image"
    echo "    args:"
    echo "      <docker_image> docker image, format as <image_name>[:<tag>], if <tag> not provided, the default tag is latest"
    echo "    options:"
    echo "      -q quiet mode, if set, will not show the download progress"
}

function usage_images(){
    echo "  $(basename $0) images [--all|-a]"
    echo "    list downloaded images on the host."
    echo "    options:"
    echo "      --all|-a show all layers"
}

function usage_ps(){
    echo "  $(basename $0) ps"
    echo "    list running docker containers on the host."
    echo "    options:"
    echo "      --all|-a list all docker containers on the host. even not in running state"
}

function usage_help(){
    echo "  $(basename $0) help <sub_command>"
    echo "    show docker sub command usage"
    echo "    args:"
    echo "      sub_command: sub command, ${support_cmds}"
}


function usage_run(){
    echo "  docker run [-n] [[-k] -f <payload_file>] [--ports|-p <ports>] [--uuid <container_uuid>] [--name <name>] [--hostname <hostname>] [--memory|-m <memory>] [--cpu_cap <cpu_cap>] [--cpu_shares <cpu_shares>] [--io <io_priority>] [--quota <quota>] [--network <network_name>] [--ip <ip>] [--nic_tag <nic_tag>] [--gateway <gateway>] [--vlan <vlan_id>] [--resolver <resolver>] [--lofs_volume|-v <lofs_volume>] [--workdir <workdir>] [--env|-e <env>] [--entrypoint <entrypoint>] [--cmd <cmd>] [--image_uuid <image_uuid> | <docker_image>]"

    echo """    create a new payload file at the current directory named with <container_uuid>.json and create the docker container. if -n option is setted, then only create a new payload file and NOT create the docker container.
    if any arg where -f <payload_file> and options both setted. the options's value is used. and the options's value will override the arg on the new payload file.
    ***volume lofs filesystems*** the lofs-volume source filesystem creatition followed by the rules:
        1. if source path is exists on SmartOS GZ, doing nothing, else go next.
        1. if source is create-volume , then create local dataset, see as lofs_volume below. else go next.
        1. if source parent is an exists filesystem, then create all sub-filesystem on the parent-filesystem. else go next
        1. if source can't find an exists parent filesystem, failed.
            - failed as current action.
            - maybe create the source path on SmartOS GZ insteed create filesystem?
            - config a base filesystem, all lofs volume be create on the filesystem, like <base_filesystem>/<vm_uuid>/sub_path?"""
    echo ""
    echo "    args:"
    echo "      <docker_image> docker image, if provide the arg, must be the end of the command.if --uuid <image_uuid> is setted, the arg will be ignored."
    echo "          format as <image_name>[:<tag>], if <tag> not provided, then set tag as latest"
    echo "    options:"
    echo "      -f <payload_file> payload file, base payload file"
    echo "      -k skip payload file property replace, only used with -f"
    echo "      -n skip create docker container, only genarate payload file"
    echo "      --ports|-p <ports> export port, formart as -p 80 or -p \"80 443\""
    echo "          - can set many times"
    echo "          - if setted, only the special ports can be accessed outside the docker container."
    echo "          - default all ports can be accessed outside the docker container."
    echo "      --uuid <container_uuid> special the docker container's uuid, if not set, a randon uuid will be set"
    echo "      --name <name> special the docker container's name, if not set, default is -."
    echo "      --hostname <hostname> special the docker container's hostname, if not set, default is the container's uuid"
    echo "      --memory|-m <memory> special the docker container's memory, unit is MB, if not set, default is 512."
    echo "      --cpu_cap <cpu_cap> Sets a limit on the amount of CPU time for the docker container, The unit used is the percentage of a single CPU that can be used, Eg. 300 means up to 3 full cpus, 0 means no limited"
    echo "      --cpu_shares <cpu_shares> Sets a limit on the number of fair share scheduler (FSS) CPU shares for docker container. the container with 50 will get 5x as much time from the scheduler as the one with 10 when there is contention."
    echo "      --io <io_priority> Sets an IO throttle priority value relative to other zones, If one zone has a value X and another zone has a value 2X, the zone with the X value will have some of its IO throttled when both try to use all available IO. default value is 100."
    echo "      --quota <quota> Sets a quota on the zone filesystem for the docker container. the unit is GB"
    echo "      --network <network_name> Sets network for the docker container. default value is default"
    echo "      --ip <ip> Sets ip address for the docker container. default value is dhcp"
    echo "          format as:"
    echo "              xxx.xxx.xxx.xxx/xx"
    echo "              xxx.xxx.xxx.xxx the netmask read from the selected network configuration, if not configed, return 24."
    echo "              dhcp from dhcp server, and then the ip will be set as static ip"
    echo "      --nic_tag <nic_tag> Sets the nic_tag for docker container, default read from the selected network configuration, if not configed, return admin"
    echo "      --gateway <gateway> Sets the gateway for docker container, default read from the selected network configuration, if not configed, will be ignored."
    echo "          format as xxx.xxx.xxx.xxx"
    echo "      --vlan <vlan_id> Sets the vlan_id for docker container, default read from the selected network configuration, if not configed, will be ignored."
    echo "      --resolver <resolver> Sets the vlan_id for docker container, default read from the selected network configuration"
    echo "          format as <ips>"
    echo "              xxx.xxx.xxx.xxx"
    echo "              xxx.xxx.xxx.xxx xxx.xxx.xxx.xxx"
    echo "          can be set many times"
    echo "          if not set and network not configed, default is: \"8.8.8.8 4.4.4.4\""
    echo "      --lofs_volume|-v <lofs_volume> Sets lofs volume for the docker container."
    echo "          volume mount as filesystems"
    echo "          can set many times"
    echo "          format as [<source>]:<target>[:<options>] or [<source>]:<target>[:<options>] [<source>]:<target>[:<options>] ..."
    echo "              source: directory or file on SmartOS GZ"
    echo "                  if the source not start with /, then the source path is \${volume_base_store}/\${source}, volume_base_store is configed in smartos-docker.conf file."
    echo "                  on new SmartOS(PI >= 20210506T001621Z) can be omited, SmartOS GZ screate a zfs filesystem on: /zones/<vm_uuid>/volumes/<volume_uuid>"
    echo "                  if the source not exists in SmartOS GZ"
    echo "                      Directory the source directory will be auto-created before docker container creating."
    echo "                          create as zfs dataset, failed if create zfs dataset failed"
    echo "                      File if the directory path not exists, the directory will be auto-created before docker container creating."
    echo "                          create as zfs dataset, failed if create zfs dataset failed."
    echo "                          copy the container target file to the source path."
    echo "                  if source not exists at docker container creating and target has owner flag. then after source created the chown will be executed. "
    echo "              target: the mount path in docker container. format as <path>[*flag[*flag]]"
    echo "                  path the mount path in docker container"
    echo "                  flag target flag"
    echo "                      f file flag, means the volume is a file, the flag must be first flag, if not set the flag, means the volume is a directory"
    echo "                      <uid>[/<gid>] owner flag, means volume's owner."
    echo "                          1000 act chown -R 1000"
    echo "                          /1000 act chown -R :1000"
    echo "                          1000/1000 act chown -R 1000:1000"
    echo "              options: mount options, just lofs mount options"
    echo "      --kernel_version <kernel_version> linux kernel version, default value is 4.3.0(ubuntu-20.04)"
    echo "      --workdir <workdir> Sets work dir for the docker container, default read from the docker image."
    echo "      --env|-e <env> Sets environment variables for the docker container."
    echo "          can be set many times"
    echo "          format as --env var1=value1 or --env \"var1=value1 var2=value2\""
    echo "      --entrypoint <entrypoint> Sets Entrypoint for the docker container"
    echo "          can be set many times. the follows is equal."
    echo "          --entrypoint bash --entrypoint scriptfile"
    echo "          --entrypoint \"bash scriptfile\""
    echo "      --cmd <cmd> Sets Cmd for the docker container"
    echo "          can be set many times. the follows is equal."
    echo "              --cmd bash --cmd scriptfile"
    echo "              --cmd \"bash scriptfile\""
    echo "      --image_uuid <image_uuid> docker image uuid, if the option setted, the <image_name> arg will be ignored"
}

function usage_start(){
    echo "  $(basename $0) start <container>"
    echo "    start docker container, ensure docker container status on running."
    echo "    args:"
    echo "      <container> docker container name or uuid"
}

function usage_version(){
    echo "  $(basename $0) version"
    echo "    show smart-docker tool's version."
}

function usage_exec(){
    echo "  $(basename $0) exec [-i] <container> <cmd>"
    echo "    exec cmd in docker container."
    echo "    args:"
    echo "      <container> docker container name or uuid"
    echo "      <cmd> the command exected in docker container"
    echo "    options:"
    echo "      -i forces interactive mode"
}

function usage_stop(){
    echo "  $(basename $0) stop [-f|-t <timeout>] <container>"
    echo "    stop docker container, ensure docker container status on stopped."
    echo "    args:"
    echo "      <container> docker container name or uuid"
    echo "    options:"
    echo "      -f force stop, not friendly"
    echo "      -t <timeout> timeout time, unit is s, default is 60"
}

function usage_restart(){
    echo "  $(basename $0) restart [-f] <container>"
    echo "    restart docker container, ensure docker container status from stopped to running."
    echo "    stop the container first. and the start the container"
    echo "    args:"
    echo "      <container> docker container name or uuid"
    echo "    options:"
    echo "      -f force restart"
}

function usage_rm(){
    echo "  $(basename $0) rm [-f] <container>"
    echo "    remove/delete docker container, the volume would not be deleted after container deleted."
    echo "    if the container's status is in runnging, it can't be deleted. you can add -f flag to force delete a running container."
    echo "    args:"
    echo "      <container> docker container name or uuid"
    echo "    options:"
    echo "      -f force delete container, even the container is in running status"
}

function usage_port(){
    echo "  $(basename $0) port [-a <ports>] [-d <ports>] <container>"
    echo "    add/remove container ports for access from outside."
    echo "    args:"
    echo "      <container> docker container name or uuid"
    echo "    options:"
    echo "      -a <ports> add ports for access from outside."
    echo "          - format as -a 80 or -a \"80 443\""
    echo "      -d <ports> remove ports for access from outside."
    echo "          - format as -d 80 or -d \"80 443\""
}

function usage(){
    echo "Usage"
    echo "  $(basename $0) --help|-h"
    echo "  $(basename $0) run [-n] [[-k] -f <payload_file>] [--ports|-p <ports>] [--uuid <container_uuid>] [--name <name>] [--hostname <hostname>] [--memory|-m <memory>] [--cpu_cap <cpu_cap>] [--cpu_shares <cpu_shares>] [--io <io_priority>] [--quota <quota>] [--network <network_name>] [--ip <ip>] [--nic_tag <nic_tag>] [--gateway <gateway>] [--vlan <vlan_id>] [--resolver <resolver>] [--lofs_volume|-v <lofs_volume>] [--kernel_version <kernel_version>] [--workdir <workdir>] [--env|-e <env>] [--entrypoint <entrypoint>] [--cmd <cmd>] [--image_uuid <image_uuid> | <docker_image>]"
    echo "  $(basename $0) logs [-f] <container>"
    echo "  $(basename $0) pull [-q] <docker_image>"
    echo "  $(basename $0) ps [--all|-a]"
    echo "  $(basename $0) images [--all|-a]"
    echo "  $(basename $0) rmi <image_uuid>"
    echo "  $(basename $0) start <container>"
    echo "  $(basename $0) stop [-f|-t <timeout>] <container>"
    echo "  $(basename $0) restart [-f] <container>"
    echo "  $(basename $0) rm [-f] <container>"
    echo "  $(basename $0) port [-a <ports>] [-d <ports>] <container>"
    echo "  $(basename $0) version"
    echo "  $(basename $0) exec [-i] <container> <cmd>"
    echo "  $(basename $0) help <sub_command>"
    exit $1
}


function fatal(){
  echo "$1"
  exit 1
}

function fatal_with_usage(){
    echo "$1"
    echo ""
    usage 1
}

# 根据路径获取要创建的 fs名称
# 如果路径为 create-volume 或已存在, 输出 E
# 如果路径找不到对应的父级 fs, 则返回""
# 如果路径找得到父级 fs, 则返回对应的要创建的 fs 名称
function get_new_zfs_name(){
    local host_path=$1
    host_path=${host_path%/}

    if [ "${host_path}" == "create-volume" -o -e ${host_path} ]; then
        echo "E"
        return
    fi


    local new_path=""
    local zfs_name=""
    while [ "${host_path}" != "" ]; do
        zfs_name=$(zfs list -H -o name ${host_path} 2>/dev/null) || true
        if [ "${zfs_name}" != "" ]; then
            break
        fi
        local last_seg=${host_path##*/}
        new_path="/${last_seg}${new_path}"
        host_path=${host_path%/*}
    done

    [[ "${zfs_name}" == "" ]] || zfs_name="${zfs_name}${new_path}"
    echo "${zfs_name}"
}

function get_volume_base_store(){
    local result=""
    [[ ! -f ${smartos_docker_conf_file} ]] || result=$(json -f ${smartos_docker_conf_file} volume_base_store)
    echo $result
}

function get_network(){
    local network_name=$1
    local network_conf=""
    if [ -f ${smartos_docker_conf_file} ]; then
        network_conf=$(json -f ${smartos_docker_conf_file} networks | json -c "this.name=='${network_name}'" 0)
    fi
    if [ "${network_conf}" == ""  -a "$2" != "" ]; then
        fatal "no netowrk found with name ${network_name}"
    fi
    echo ${network_conf}
}

function get_vol_string(){
    local lofs_basename=$1
    local vol=$2
    local vol_seg=()
    local vol_string="{"

    if [ "$(echo ${vol}|grep :)" != "" ]; then
        for ((i=1;i<=3;i++)) do
            vol_seg[i-1]=$(echo ${vol} | cut -d ":" -f $i)
        done
    fi

    local src=${vol_seg[0]}
    # new pi support
    [[ "${src}" != "" ]] || src="create-volume"
    if [ "${src:0:1}" != "/" ]; then
        local lofs_store_base=$(get_volume_base_store)
        if [ "${src}" == "create-volume" ]; then
            [[ "${lofs_store_base}" == "" ]] || src="${lofs_store_base}/${lofs_basename}/volumes/$(uuid)"
        else
            [[ "${lofs_store_base}" != "" ]] || fatal "no volume_base_store config find. invalid volume source: ${src}"
            src="${lofs_store_base}/${lofs_basename}/volumes/${src}"
        fi
    fi

    local tgt=${vol_seg[1]}
    [[ "${tgt}" != "" ]] || fatal "${vol} 设置不对, lofs_volume的正确格式为: '[<source>]:<target>[:<options>]\\n如: /zones/volumes/data:/data\\n/zones/volumes/data:/data:ro\\n:/data\\n"


    if [ "$(echo ${tgt}|grep \*)" != "" ]; then
        local temp_val=${tgt}
        tgt=$(echo ${temp_val} | cut -d "*" -f 1)
        local temp_flag=$(echo ${temp_val} | cut -d "*" -f 2)
        local temp_result='{}'
        if [ "${temp_flag}" == "f" ];then
            temp_result=$(echo ${temp_result} | json -e "this.isfile=true")
            temp_flag=$(echo ${temp_val} | cut -d "*" -f 3)
        fi
        [[ "${temp_flag}" == "" ]] || temp_result=$(echo ${temp_result} | json -e "this.owner='${temp_flag/\//:}'")

        if [ "${temp_result}" != "{}" ]; then
            temp_result=$(echo ${temp_result} | json -e "this.source='${src}';this.target='${tgt}'")
            if [ "${lofs_attr}" == "" ];then
                lofs_attr=${temp_result}
            else
                lofs_attr="${lofs_attr}, ${temp_result}"
            fi
        fi
    fi


    $(echo ${vol} | cut -d ":" -f $i)

    local options=${vol_seg[2]}

    vol_string="${vol_string}\"source\":\"${src}\""
    vol_string="${vol_string},\"type\":\"lofs\""
    vol_string="${vol_string},\"target\":\"${tgt}\""
    if [ "${options}" != "" ]; then
        vol_string="${vol_string},\"options\":[\"${options//,/\",\"}\"]"
    fi
    g_vol_string="${vol_string}}"
}

function gen_dhcp_ip(){
    local network_name=$1
    local network_conf=$(get_network ${network_name} "throw")
    local temp_nic_tag=$(echo ${network_conf} | json tag)
    local temp_nic_gateway=$(echo ${network_conf} | json gw)
    local temp_nic_vlan_id=$(echo ${network_conf} | json vlan_id)
    local nic_json_content="\"ips\":[\"dhcp\"],\"gateway\":\"${temp_nic_gateway}\",\"nic_tag\":\"${temp_nic_tag}\""
    [[ "${temp_nic_vlan_id}" == "" ]] || nic_json_content="${nic_json_content},\"vlan_id\": ${temp_nic_vlan_id}"

    local temp_vm_uuid=$(uuid)
    local vm_json="{\"uuid\":\"${temp_vm_uuid}\",\"image_uuid\":\"${temp_image_uuid}\",\"brand\":\"joyent-minimal\",\"nics\":[{${nic_json_content}}],\"alias\":\"get-dhcp-ip\"}"

    [[ "$(imgadm list -H -o name uuid=${temp_image_uuid})" != "" ]] || imgadm import -q ${temp_image_uuid}

    #echo "create temp vm for get dhcp ip"
    echo ${vm_json}|vmadm create 2>/dev/null
    local dhcp_ip=$(zlogin ${temp_vm_uuid} 'ipadm show-addr -p -o addr net0/')
    vmadm delete ${temp_vm_uuid} 2>/dev/null

    echo "$dhcp_ip"
}

json_context=""

function is_uuid(){
    local val=$1
    if [ ${#val} != 36 ]; then
        echo 0
        return
    fi
    local chars="[a-fA-F0-9]"
    local pattern="${chars}\{8\}\(-${chars}\{4\}\)\{3\}-${chars}\{12\}"
    local result=$(echo ${val} | grep ${pattern})
    if [ "${result}" == "${val}" ]; then
        echo 1
    else
        echo 0
    fi
}

function apply_network(){
    local network_name=$1
    local network_conf=$(get_network ${network_name} "throw")
    [[ "${nic_tag}" != "" ]] || nic_tag=$(echo ${network_conf} | json tag)
    [[ "${nic_gateway}" != "" ]] || nic_gateway=$(echo ${network_conf} | json gw)
    [[ "${nic_vlan_id}" != "" ]] || nic_vlan_id=$(echo ${network_conf} | json vlan_id)
    [[ "${nic_netmask}" != "" ]] || nic_netmask=$(echo ${network_conf} | json netmask)
    nic_netmask=$(echo ${network_conf} | json netmask)
    [[ "${#resolvers[*]}" != "0" ]] || resolvers=($(echo $network_conf|json -e 'resolvers_string=this.resolvers.join(" ")' resolvers_string))
}


function get_docker_image_uuid(){
    local docker_image=$1
    local image_repo=${docker_image%:*}
    local image_tag=${docker_image##*:}
    [[ "${image_tag}" != "${docker_image}" ]] || image_tag="latest"
    image_uuid=$(imgadm list -j --docker  docker_repo=${image_repo} docker_tags=${image_tag} |json 0.manifest.uuid)
    echo "${image_uuid}"
}

function prepare_metadata(){
    json_string=$1
    # VM
    local val=$(echo ${json_string} | json uuid)

    if [ "${val}" == "" ]; then
        json_string=$(echo ${json_string} | json -e "this.uuid='${vm_uuid}'")
    else
        vm_uuid=${val}
    fi
    [[ "${hostname}" == "" ]] || json_string=$(echo ${json_string} | json -e "this.hostname='${hostname}'")
    [[ "${name}" == "" ]] || json_string=$(echo ${json_string} | json -e "this.alias='${name}'")
    [[ "${memory}" == "" ]] || json_string=$(echo ${json_string} | json -e "this.max_physical_memory=${memory}")
    [[ "${cpu_cap}" == "" ]] || json_string=$(echo ${json_string} | json -e "this.cpu_cap=${cpu_cap}")
    [[ "${cpu_shares}" == "" ]] || json_string=$(echo ${json_string} | json -e "this.cpu_shares=${cpu_shares}")
    [[ "${quota}" == "" ]] || json_string=$(echo ${json_string} | json -e "this.quota=${quota}")
    [[ "${zfs_io_priority}" == "100" ]] || json_string=$(echo ${json_string} | json -e "this.zfs_io_priority=${quota}")

    [[ "${nic_ip}" == "" ]] || json_string=$(echo ${json_string} | json -e "this.nics[0].ips=['${nic_ip}']")
    [[ "${nic_tag}" == "" ]] || json_string=$(echo ${json_string} | json -e "this.nics[0].nic_tag='${nic_tag}'")
    [[ "${nic_gateway}" == "" ]] || json_string=$(echo ${json_string} | json -e "this.nics[0].gateway='${nic_gateway}'")
    [[ "${nic_vlan_id}" == "" ]] || json_string=$(echo ${json_string} | json -e "this.nics[0].vlan_id=${nic_vlan_id}")

    if [ "${#resolvers[*]}" != "0" ]; then
        val=${resolvers[@]}
        json_string=$(echo ${json_string} | json -e "this.resolvers=['${val// /\',\'}']")
    fi


    if [ "${#lofs_volumes[*]}" != "0" ]; then
        local lofs_basename=$(echo ${json_string} | json alias)
        [[ "${lofs_basename}" != "" ]] || lofs_basename=${vm_uuid}
        vol_prefix=','
        # TODO: replace exists filesystems
        #file_systems=$(echo ${json_string} | json filesystems)
        if [ "${file_systems}" == "" ]; then
            file_systems='[]'
            vol_prefix=''
        fi
        for vol in ${lofs_volumes[@]}
        do
            g_vol_string=""
            get_vol_string ${lofs_basename} ${vol}
            file_systems="${file_systems%%]}${vol_prefix}${g_vol_string}]"
            vol_prefix=','
        done

        json_string=$(echo ${json_string} | json -e "this.filesystems=${file_systems}")
    fi

    json_string=$(echo ${json_string} | json -e "this.docker=true; this.brand='lx'; this.kernel_version='${kernel_version}'")

    # DOCKER
    [[ "${image_uuid}" != "" ]] || image_uuid=$(echo ${json_string} | json image_uuid)
    [[ "$(imgadm list -H --docker uuid=${image_uuid})" != "" ]] || fatal "docker image_uuid ${image_uuid} not exists"

    if [ "${#entrypoints[*]}" == "0" ]; then
        docker_entrypoint=$(imgadm get ${image_uuid}|json manifest.tags.docker:config.Entrypoint -o json-0|sed 's/"/\\"/g')
    else
        val=${entrypoints[@]}
        docker_entrypoint="[\\\"${val// /\\\",\\\"}\\\"]"
    fi
    if [ "${#cmds[*]}" == "0" ]; then
        docker_cmd=$(imgadm get ${image_uuid}|json manifest.tags.docker:config.Cmd -o json-0|sed 's/"/\\"/g')
    else
        val=${cmds[@]}
        docker_cmd="[\\\"${val// /\\\",\\\"}\\\"]"
    fi

    # docker_env=$(imgadm get ${image_uuid}|json manifest.tags.docker:config.Env -o json-0|sed 's/"/\\"/g')
    docker_env=$(imgadm get ${image_uuid}|json manifest.tags.docker:config.Env -o json-0)

     [[ "${docker_env}" != "null" ]] || docker_env=""
    #docker_env=""
    if [ "${#envs[*]}" != "0" ]; then
        [[ "${docker_env}" != "" ]] || docker_env="[]"
        for env in ${envs[@]}
        do
            env_key=${env%%=*}
            env_exists=$(echo ${docker_env}|grep "${env_key}=") || env_exists=""
            if [ "${env_exists}" == "" ]; then
                docker_env="${docker_env%%]},\"${env}\"]"
            else
                docker_env=$(echo ${docker_env} | sed "s|\"${env_key}=[^\"]*\"|\"${env}\"|")
            fi
        done
        # docker_env="[\\\"${val// /\\\",\\\"}\\\"]"
        docker_env=$(echo ${docker_env}|sed 's/"/\\"/g')
    fi

    #docker_workingdir=$(imgadm get ${image_uuid}|json manifest.tags.docker:config.WorkingDir -o json-0|sed 's/"/\\"/g')
    docker_workingdir=$(imgadm get ${image_uuid}|json manifest.tags.docker:config.WorkingDir -o json-0|sed 's/"/\\"/g')


    [[ "${docker_workingdir}" != '\"\"' ]] || docker_workingdir=
    [[ "${workdir}" != "" ]] || workdir=${docker_workingdir}

    internal_metadata=""
    [[ "${docker_entrypoint}" != "null" ]] || docker_entrypoint=""
    [[ "${docker_cmd}" != "null" ]] || docker_cmd=""

    prefix='{'
    if [ "${docker_entrypoint}" != "" ]; then
        internal_metadata="${internal_metadata}${prefix}'docker:entrypoint':'${docker_entrypoint}'"
        prefix=','
    fi
    if [ "${docker_cmd}" != "" ]; then
        internal_metadata="${internal_metadata}${prefix}'docker:cmd':'${docker_cmd}'"
        prefix=','
    fi
    if [ "${docker_env}" != "" ]; then
        internal_metadata="${internal_metadata}${prefix}'docker:env':'${docker_env}'"
        prefix=','
    fi
    if [ "${workdir}" != "" ]; then
        internal_metadata="${internal_metadata}${prefix}'docker:workingdir':'${workdir}','docker:workdir':'${workdir}'"
        prefix=','
    fi
    internal_metadata="${internal_metadata}${prefix}'docker:open_stdin': true, 'docker:tty': true"

    if [ "${lofs_attr}" != "" ];then
        lofs_attr="[${lofs_attr}]"
        internal_metadata="${internal_metadata},'${internal_namespace}:lofs_attr':\"$(echo ${lofs_attr}|sed 's/"/\\"/g')\""
    fi

    if [ "${#ports[*]}" != "0" ]; then
        val=${ports[@]}
        internal_metadata="${internal_metadata},'${internal_namespace}:ports':\"${val}\""
        json_string=$(echo ${json_string} | json -e "this.firewall_enabled=true")
    fi

    internal_metadata="${internal_metadata},'${internal_namespace}:network': \"${nic_network}\"}"

    json_code="this.internal_metadata_namespaces=[\"${internal_namespace}\"]; this.internal_metadata=${internal_metadata}; "

    json_string=$(echo ${json_string} | json -e "${json_code}")




    json_context=${json_string}
}


function do_pull(){
    local img=$1
    if [ "${pull_quiet}" == "true" ];then
        imgadm import -q ${img}
    else
        imgadm import ${img}
    fi
}

function do_ps(){
    local list_all=$1
    if [ "${list_all}" == "-a" -o "${list_all}" == "--all" ]; then
        vmadm list -o uuid,type,ram,cpu_cap,cpu_shares,quota,state,alias docker=true
    else
        vmadm list -o uuid,type,ram,cpu_cap,cpu_shares,quota,state,alias docker=true state=running
    fi
}

function do_images(){
    imgadm list --docker
}

function do_images_all(){
    imgadm list -o uuid,docker_repo,docker_tags,version,pub type=docker
}

function get_docker_uuid(){
    local zone=$1
    if [ "$(is_uuid ${zone})" == "0" ]; then
        zone=$(vmadm lookup docker=true alias=${zone})
    else
        zone=$(vmadm lookup docker=true uuid=${zone})
    fi
    [[ "${zone}" != "" ]] || fatal "docker \"$1\" not exists or not a docker container."
    echo "${zone}"
}

function do_logs(){
    local zone=$(get_docker_uuid $1)
    if [ "${log_follow}" ==  "true" ]; then
        tail -f /zones/${zone}/logs/stdio.log | bunyan
    else
        cat /zones/${zone}/logs/stdio.log | bunyan
    fi
}

function do_start(){
    local zone=$(get_docker_uuid $1)
    vmadm start ${zone}
}

function do_version(){
    echo "smartos-docker version is ${VERSION}"
}

function do_exec(){
    local flag_i=""
    if [ "$1" == "-i" ]; then
        flag_i="-i"
        shift
    fi
    local zone=$(get_docker_uuid $1)
    shift 
    
    local cmd=$@
    if [ "$cmd" == "" ]; then
        echo "there has no cmd with exec sub command."
        usage_exec
    fi

    zlogin ${flag_i} ${zone} /native/usr/vm/sbin/dockerexec ${cmd}
}

function do_stop(){
    local zone=$(get_docker_uuid $1)
    local options=""
    [[ "${stop_force}" !=  "true" ]] || options=" -F "
    [[ "${stop_timeout}" ==  "" ]] || options="${options} -t ${stop_timeout}"
    vmadm stop ${zone} ${options}
}



function do_restart(){
    local zone=$(get_docker_uuid $1)
    local options=""
    [[ "${restart_force}" !=  "true" ]] || options=" -F "

    vmadm reboot ${zone} ${options}
}

function do_rm(){
    local zone=$(get_docker_uuid $1)
    local state=$(vmadm get ${zone} | json state)
    if [ "${state}" == "running" -a "${rm_force}" !=  "true"  ]; then
        fatal "docker $1 is in ${state} status, please stop it or with -f option"
    fi

    vmadm delete ${zone}
    echo "delete firewall rule..."
    ensure_fwrule_removed ${zone}
}

function do_rmi(){
    local img_uuid=$1
    imgadm delete ${img_uuid}
}

function do_help(){
    local cmd=$1
    case ${cmd} in
        run|pull|ps|logs|images|start|stop|restart|rm|rmi|port|version|exec|help )
            echo "Usage"
            usage_${cmd}
            exit
            ;;
        * )
            echo "not supported command: ${cmd}"
            usage
            ;;
    esac
}


function do_run(){
    if [ ${skip_prepare} == true -a "${payload_file}" == "" ]; then
        fatal "-k must be used with -f <payload_file>"
    fi
    if [ "${payload_file}" == "" ]; then
        payload_file=docker-payload.json
        cat > ${payload_file} <<EOF
{
    "uuid": "${vm_uuid}",
    "max_physical_memory": 512,
    "image_uuid": "${image_uuid}",
    "resolvers": [
        "8.8.8.8","4.4.4.4"
    ],
    "nics": [
        {
            "nic_tag": "admin",
            "ips": [
                "dhcp"
            ],
            "primary": true
        }
    ]
}
EOF
    fi

    new_payload_file=${payload_file}
    if [ ${skip_prepare} == false ]; then
        prepare_metadata "$(json -f ${payload_file})"
        new_payload_file=${vm_uuid}.json
        echo ${json_context} | json > ${new_payload_file}
        echo "payload file saved at ${new_payload_file}"
    else
        vm_uuid=$(json -f ${payload_file} uuid)
    fi

    if [ "${run_type}" == "run" ]; then
        ensure_volume ${new_payload_file}

        ip=$(json -f ${new_payload_file} nics.0.ips.0)
        if [ "${ip}" == "dhcp" ]; then
            temp_network=$(json -f ${new_payload_file} internal_metadata.${internal_namespace}:network)
            [[ "${temp_network}" == "" ]] || nic_network=${temp_network}
            [[ "${nic_network}" != "" ]] || fatal "dhcp must with --network"
            ip=$(gen_dhcp_ip ${nic_network})
            [[ "${ip}" != "" ]] || fatal "can not get ${nic_network} ip address from dhcp server"
            temp_json_content=$(json -f ${new_payload_file} -e "nics[0].ips[0]='${ip}'")
            echo "docker container's ip is ${ip}"
            echo ${temp_json_content} | json > ${new_payload_file}
        fi
        local ports=$(json -f ${new_payload_file} internal_metadata.${internal_namespace}:ports)

        if [ "${ports}" != "" -a "$(json -f ${new_payload_file} firewall_enabled)" != "true" ]; then
            temp_json_content=$(json -f ${new_payload_file} -e "firewall_enabled=true")
            echo ${temp_json_content} | json > ${new_payload_file}
        fi

        vmadm create -f ${new_payload_file}

        if [ "${ports}" != "" ]; then
            ensure_ports ${vm_uuid} "${ports}"
        fi
    elif [ "${run_type}" == "show" ]; then
        echo ""
        json -f ${new_payload_file}
    fi
}

function ensure_fwrule_removed(){
    local vm_uuid=$1
    local pattern="FROM any TO vm ${vm_uuid} ALLOW"
    local rules=$(fwadm list -j |json -c "this.rule.indexOf('${pattern}')==0" -a uuid)
    if [ "${rules}" != "" ]; then
        for rule_uuid in ${rules}
        do
            fwadm delete ${rule_uuid} 1>/dev/null
        done
    fi
}

function do_remove_ports(){
    local vm_uuid=$(get_docker_uuid $1)
    
    shift
    local ports="$@"
    if [ "${ports}" != "" ]; then
        local rules=""
        local pattern=""
        echo "remove ports ${ports}..."
        for port in ${ports}
        do
            pattern="FROM any TO vm ${vm_uuid} ALLOW tcp PORT ${port}"
            rules=$(fwadm rules -j ${vm_uuid} |json -c "this.rule=='${pattern}'" -a uuid)
            for rule_uuid in ${rules}
            do
                fwadm delete ${rule_uuid} 1>/dev/null
            done
        done
    fi
}

function do_add_ports(){
    local vm_uuid=$(get_docker_uuid $1)
    

    shift
    local ports="$@"
    if [ "${ports}" != "" ]; then

        echo "add ports ${ports}..."
        ensure_ports ${vm_uuid} "${ports}"
    fi 
}

function ensure_ports(){
    local vm_uuid=$1
    local ports="$2"
    local firewall_enabled=$(vmadm get ${vm_uuid}|json firewall_enabled)    
    if [ "${ports}" != "" ]; then       
        [[ "${firewall_enabled}" == "true" ]] || fwadm start ${vm_uuid}
        local pattern=""
        local rules=""
        for port in ${ports}
        do
            pattern="FROM any TO vm ${vm_uuid} ALLOW tcp PORT ${port}"
            rules=$(fwadm rules -j ${vm_uuid} |json -c "this.rule=='${pattern}'" -a uuid)
            [[ "${rules}" != "" ]] || fwadm add -g -e ${pattern} 1>/dev/null
        done
    fi
}

function ensure_volume(){
    local filename=$1
    local volume_attrs=$(json -f ${filename} internal_metadata.${internal_namespace}:lofs_attr)
    [[ "${volume_attrs}" != "" ]] || volume_attrs="[]"
    local file_not_exists=""
    local zfs_names=""
    for src in $(json -f ${filename} filesystems|json -a source)
    do
        local isfile=$(echo ${volume_attrs} | json -c "source=='${src}'" 0.isfile)
        if [ "${isfile}" == "true" ]; then
            [[ -f ${src} ]] || file_not_exists="${file_not_exists} ${src}"
            continue
        fi

        zfs_name=$(get_new_zfs_name ${src})
        [[ "${zfs_name}" != "" ]] || fatal "filesystems source ${src} can't be created"
        [[ "${zfs_name}" == "E" ]] || zfs_names="${zfs_names} ${zfs_name}"
    done

    if [ "${zfs_names}" != "" ];then
        for zfs_name in ${zfs_names}
        do
            zfs create -p ${zfs_name}
        done
    fi

    if [ "${file_not_exists}" != "" ]; then
        local temp_vm_uuid=$(uuid)
        echo "create temp docker for cp default config file..."
        json -f ${filename} -e "this.filesystems=null;this.uuid='${temp_vm_uuid}'"|sed 's/"filesystems": null,//'| vmadm create
        for src in ${file_not_exists}
        do
            tgt=$(echo ${volume_attrs} | json -c "source=='${src}'" 0.target)
            cp /zones/${temp_vm_uuid}/root${tgt} ${src}
        done
        echo "remove temp docker..."
        vmadm delete ${temp_vm_uuid}
        echo ""
    fi


    for v_attr in $(echo ${volume_attrs} | json -e "result=this.source + '*' + this.owner" -a result)
    do
        src=$(echo ${v_attr} | cut -d '*' -f 1)
        owner=$(echo ${v_attr} | cut -d '*' -f 2)
        [[ "${owner}" != "undefined" ]] || owner=""
        echo "chown -R ${owner} ${src}"
        [[ "${owner}" == "" ]] || chown -R ${owner} ${src}
    done
}



vm_uuid=$(uuid)
kernel_version=4.3.0
run_type=run
envs=()
cmds=()
entrypoints=()
hostname=""
name=""
memory=""
cpu_cap=""
cpu_shares=""
quota=""
nic_ip=""
nic_gateway=""
nic_vlan_id=""
nic_tag=""
nic_netmask=24
resolvers=()
lofs_volumes=()
ports=()
skip_prepare=false
zfs_io_priority=100

NETWORK_DEFAULT="default"
nic_network=""
lofs_attr=""

CMD=""

case $1 in
    run|pull|ps|logs|images|start|stop|restart|rm|rmi|port|version|exec|help )
        CMD=$1
        shift 1
        ;;
esac

if [ "$1" == "-h" -o "$1" == "--help" ]; then
    usage
fi

[[ "${CMD}" != "" ]] || fatal_with_usage "invalid sub command, only supported sub commands: ${support_cmds}"

if [ "$CMD" == "run" ]; then
    # parse otions
    while [[ $# -ge 1 ]]; do
        case $1 in
            -f )
                payload_file=$2
                shift 2
                ;;
            -n )
                run_type=show
                shift
                ;;
            -k )
                skip_prepare=true
                shift
                ;;
            --uuid )
                vm_uuid=$2
                [[ "$(is_uuid ${vm_uuid})" == "1" ]] || fatal "--uuid $2 not a valid uuid"
                shift 2
                ;;
            --image_uuid )
                image_uuid=$2
                [[ "$(is_uuid ${image_uuid})" == "1" ]] || fatal "--image_uuid $2 not a valid uuid"
                shift 2
                ;;
            --workdir )
                workdir=$2
                shift 2
                ;;
            --hostname )
                hostname=$2
                shift 2
                ;;
            --name )
                name=$2
                shift 2
                ;;
            --kernel_version )
                kernel_version=$2
                shift 2
                ;;
            --memory|-m )
                memory=$2
                shift 2
                ;;
            --cpu_cap )
                cpu_cap=$2
                shift 2
                ;;
            --cpu_shares )
                cpu_shares=$2
                shift 2
                ;;
            --quota )
                quota=$2
                shift 2
                ;;
            --io )
                zfs_io_priority=$2
                shift 2
                ;;
            --ip )
                nic_ip=$2
                shift 2
                ;;
            --network )
                nic_network=$2
                shift 2
                ;;
            --gateway )
                nic_gateway=$2
                shift 2
                ;;
            --vlan_id )
                nic_vlan_id=$2
                shift 2
                ;;
            --nic_tag )
                nic_tag=$2
                shift 2
                ;;
            --resolver )
                resolvers=(${resolvers[@]} $2)
                shift 2
                ;;
            --lofs_volume|-v )
                lofs_volumes=(${lofs_volumes[@]} $2)
                shift 2
                ;;
            --env|-e )
                envs=(${envs[@]} $2)
                shift 2
                ;;
            --ports|-p )
                ports=(${ports[@]} $2)
                shift 2
                ;;
            --cmd )
                cmds=(${cmds[@]} $2)
                shift 2
                ;;
            --entrypoint )
                entrypoints=(${entrypoints[@]} $2)
                shift 2
                ;;
            -h|--help )
                usage
                ;;
            * )
                is_image_name=false
                if [ $# -eq 1 ]; then
                    [[ "${image_uuid}" != "" ]] || is_image_name=true
                fi
                if [ ${is_image_name} == true ]; then
                    image_name=$1
                    image_uuid="$(get_docker_image_uuid ${image_name})"
                    if [ "${image_uuid}" == "" ]; then
                        pull_quiet="true"
                        do_pull $1
                        image_uuid="$(get_docker_image_uuid ${image_name})"
                    fi
                    [[ "${image_uuid}" != "" ]] || fatal "${image_name} is invalid docker image"
                    shift
                else
                    echo ""
                    echo "not supported arg: $1"
                    echo ""
                    usage_${CMD}
                fi
                ;;
        esac
    done

    if [ "${image_uuid}" == "" ]; then
        if [ -f "${payload_file}" ]; then
            image_uuid=$(json -f ${payload_file} image_uuid)
        fi

        [ "${image_uuid}" != "" ] || fatal "must provide <image_uuid> or <docker_image>"
    fi

    [[ "${nic_network}" != "" ]] || nic_network=${NETWORK_DEFAULT}
    apply_network ${nic_network}
    [[ "${nic_ip}" != "" ]] || nic_ip="dhcp"
    if [ "${nic_ip}" != "dhcp" -a "$(echo ${nic_ip}|grep /)" == "" ]; then
        nic_ip="${nic_ip}/${nic_netmask}"
    fi

    # do run
    do_run
fi

if [ "$CMD" == "pull" ]; then
    [[ $# -lt 3 ]] || usage_${CMD}
    if [ "$1" == "-q" ]; then
        pull_quiet="true"
        shift
    fi
    [[ $# -eq 1 ]] || usage_${CMD}
    do_pull $1
fi

if [ "$CMD" == "ps" ]; then
    [[ $# -lt 2 ]] || usage_${CMD}
    do_ps $1
fi

if [ "$CMD" == "images" ]; then
    if [ $# -eq 0 ]; then
        do_images
    elif [ $# -eq 1 ]; then
        if [ "$1" == "--all" -o "$1" == "-a" ]; then
            ddo_images_all
        else
            fatal "$1 is invalid images command option"
        fi
    else
        fatal "images command only support -a|--all"
    fi
fi

# smartos-docker logs [-f] <docker>
if [ "$CMD" == "logs" ]; then
    [[ $# -lt 3 ]] || usage_${CMD}
    if [ "$1" == "-f" ]; then
        log_follow="true"
        shift
    fi
    [[ $# -eq 1 ]] || usage_${CMD}
    do_logs $1
fi

if [ "$CMD" == "help" ]; then
    [[ $# -eq 1 ]] || usage_${CMD}
    do_help $1
fi

if [ "$CMD" == "start" ]; then
    [[ $# -eq 1 ]] || usage_${CMD}

    do_start $1
fi

if [ "$CMD" == "restart" ]; then
    [[ $# -lt 3 ]] || usage_${CMD}
    if [ "$1" == "-f" ]; then
        restart_force="true"
        shift
    fi
    [[ $# -eq 1 ]] || usage_${CMD}
    do_restart $1
fi

if [ "$CMD" == "rm" ]; then
    [[ $# -lt 3 ]] || usage_${CMD}
    if [ "$1" == "-f" ]; then
        rm_force="true"
        shift
    fi
    [[ $# -eq 1 ]] || usage_${CMD}
    do_rm $1
fi

if [ "$CMD" == "version" ]; then
    [[ $# -eq 0 ]] || usage_${CMD}

    do_version
fi

if [ "$CMD" == "exec" ]; then
    do_exec $@
fi

if [ "$CMD" == "rmi" ]; then
    [[ $# -eq 1 ]] || usage_${CMD}
    [[ "$(is_uuid $1)" == "1" ]] || fatal "$1 not a valid uuid"
    do_rmi $1
fi

if [ "$CMD" == "port" ]; then
    add_ports=()
    remove_ports=()
    vm_uuid=""
    while [[ $# -ge 1 ]]; do
        case $1 in
            -a )
                add_ports=(${add_ports[@]} $2)
                shift 2
                ;;
            -d )
                remove_ports=(${remove_ports[@]} $2)
                shift 2
                ;;
            * )
                vm_uuid=$(get_docker_uuid $1)
                if [ $# -ne 1 ]; then
                    echo "only <container> arg accepted"
                    echo ""
                    usage_${CMD}
                    exit
                fi
                shift
                ;;
        esac
    done

    if [ "${vm_uuid}" == "" ]; then
        echo "<container> not exist or is invalid"
        echo ""
        usage_${CMD}
        exit
    fi

    do_add_ports ${vm_uuid} "${add_ports[@]}"
    do_remove_ports ${vm_uuid} "${remove_ports[@]}"

    fw_ports=($(fwadm rules -j ${vm_uuid} |json -c "this.rule.indexOf('FROM any TO vm ${vm_uuid} ALLOW')==0" -a rule | awk -F' ' '{print $NF}'))
    firewall_enabled=$(vmadm get ${vm_uuid}|json firewall_enabled)
    if [ "${fw_ports}" == "" ]; then        
        [[ "${firewall_enabled}" != "true" ]] || fwadm stop ${vm_uuid}
        echo "{\"remove_internal_metadata\":[\"${internal_namespace}:ports\"]}" |vmadm update ${vm_uuid}
    else
        vmadm update ${vm_uuid} internal_metadata.${internal_namespace}:ports="${fw_ports[@]}"
    fi
fi

if [ "$CMD" == "stop" ]; then
    while [[ $# -ge 1 ]]; do
        case $1 in
            -f )
                stop_force="true"
                shift
                ;;
            -t )
                stop_timeout=$2
                shift 2
                ;;
            * )
                if [ $# -gt 1 ]; then
                    echo ""
                    echo "not supported arg: $1"
                    echo ""
                    usage_${CMD}
                fi
                do_stop $1
                shift
                ;;
        esac
    done
fi